//Pendulum Sim 2.0

unit SimulationEngine;

interface

	uses
	  SysUtils, Classes, SimulationData, Pendulum, Windows, Math;
	
	type
		
		TSimMode = ( psmLive, psmPrecalc );
	
		TPendulumSim = Class(TObject)
		Private
		
			_localDataset : TList; //Our primary dataset!
			
			_simMode : TSimMode; //Mode of sim operation
			
			_simTimeStep : real; //Our time step in seconds
			_simLength : real; // Length of our simulation in seconds
			
			_gravitationalFieldStrength : real; // Gravitational field strength in N/Kg
			
			//_halt : boolean; // is simulation halted - **Live Only! DEPR
			
			_currentStep : TSimData; // Reference to current simulation step
			
			_lastExecutionTicks: int64;
			_currentTicks: int64;
			_timerResolution: int64;
			
			//damping?
			_dampingEnabled : boolean;
			_dampingFactor : real;
			
			_interval: real;
			
			_pendulum : TPendulum;
		Protected
		
		Published
			property Mode : TSimMode Read _simMode Write _simMode;
			property Pendulum : TPendulum Read _pendulum Write _pendulum;
			property TimeStep : Real Read _simTimeStep Write _simTimeStep;
			property Length : Real Read _simLength Write _simLength;
			
			property Gravity : Real Read _gravitationalFieldStrength Write _gravitationalFieldStrength;
			property CurrentStep : TSimData Read _currentStep Write _currentStep;
			property DampingEnabled : Boolean Read _dampingEnabled Write _dampingEnabled;
			property DampingFactor : Real Read _dampingFactor Write _dampingFactor;
		Public
			Constructor Create(SimulationLength, TimeStep, Gravity : Real; Pendulum : TPendulum; Mode: TSimMode); Overload;
			Constructor Create(SimulationLength, TimeStep, Gravity, InitialInclination, InitialAngularVelocity : Real; Pendulum : TPendulum; Mode: TSimMode); Overload;
			
			Function RunStep : TSimData;
			procedure Reset(InitialInclination, InitialAngularVelocity : Real);
			
			Function GetMaxes(iterCount : Integer) : TSimData;
			
		end;
	

  

implementation
	
	Constructor TPendulumSim.Create(SimulationLength, TimeStep, Gravity, InitialInclination, InitialAngularVelocity : Real; Pendulum : TPendulum; Mode: TSimMode);
	var
		InitialData : TSimData;
	begin
		//Set initial parameters
		_simLength := SimulationLength;
		_simTimeStep := TimeStep;
		_gravitationalFieldStrength := Gravity;
		
		QueryPerformanceFrequency(_timerResolution);
		_lastExecutionTicks := 0;
		_currentTicks := 0;
		
		_pendulum := Pendulum;
		_simMode := Mode;
		
		//Initial damping;
		_dampingEnabled := false;
		_dampingFactor := 0;
		
		//Create local dataset
		_localDataset := TList.Create;
		
		//Set initial data
		InitialData := TSimData.Create(0, InitialInclination, InitialAngularVelocity);
		CurrentStep := InitialData;
		//Add initial data to dataset
		_localDataset.Add(InitialData);
	end;

	Constructor TPendulumSim.Create(SimulationLength, TimeStep, Gravity : Real; Pendulum : TPendulum; Mode: TSimMode);
	begin
		//We just call our other constructor
		Self.Create(SimulationLength, TimeStep, Gravity, 0, 0, Pendulum, Mode); 
	end;
	
	
	//RunStep
	//-------
	//When run every _simTimeStep seconds, it returns a SimData object containing the current
	//inclination and angular velocity of the pendulum
	//The code inside uses a high resolution timer for more accurate calculation
	//The error property of the SimData object stores the difference between the execution time
	//and _simTimeStep in seconds
	Function TPendulumSim.RunStep : TSimData;
	var
		SimData : TSimData;
	begin
		SimData := TSimData.Create;
		//For a live simulation we keep running it at regular time intervals
		//The actual time step will be calculated dynamically from the last execution time
		QueryPerformanceCounter(_currentTicks);
		if _simMode = psmLive then
		begin
			if (_lastExecutionTicks <> 0) then
			begin
				_interval := (_currentTicks - _lastExecutionTicks)/_timerResolution; //Calculate the interval since the last execution in seconds
				if abs(_interval - _simTimeStep)*1000 > 20 then _interval := _simTimeStep; //If our error is greater than 20ms, set the _interval to our timestep
			end
			else 
			_interval := _simTimeStep; //if we're at the start of the execution, then set the interval to the "official" timestep
			
			

			SimData.Time := TSimData(_localDataset.Last).Time + _interval; //The time from the start of our simulation
			//Calculate the inclination
			SimData.Inclination := _pendulum.CalculateInclination(TSimData(_localDataset.Last).Inclination, TSimData(_localDataset.Last).AngularVelocity, _interval); 
			//Calculate the angular velocity
			if not _dampingEnabled then
			SimData.AngularVelocity := _pendulum.CalculateAngularVelocity(TSimData(_localDataset.Last).AngularVelocity, SimData.Inclination, _interval, _gravitationalFieldStrength)
			else
			SimData.AngularVelocity := _pendulum.CalculateAngularVelocityWithDamping(TSimData(_localDataset.Last).AngularVelocity, SimData.Inclination, TSimData(_localDataset.Last).Inclination, _dampingFactor, _interval, _gravitationalFieldStrength);
			
			//Calculate the difference between the calculated execution interval and timestep
			SimData.Error := abs(_interval - _simTimeStep); //error in seconds
			
			_localDataset.Add(SimData); //Add the simulation data to our local dataset
			
		end;
		_lastExecutionTicks := _currentTicks;
        RunStep := SimData;
	end;
	
	//Function returns the maximum angular velocity, and maximum inclination for the simulation
	Function TPendulumSim.GetMaxes(iterCount : Integer) : TSimData;
	var
		simData : TSimData;
		_inclination, _angularV : real;
		i: integer;
	begin
		//Create empty simdata
		simData:= TSimData.Create(0,0,0);
		
		//Get the last simulation data
		_inclination := TSimData(_localDataset.Last).Inclination;
		_angularV := TSimData(_localDataset.Last).AngularVelocity;
		
		for i:= 1 to iterCount do
		begin
		//Calculate the inclination
		_inclination := _pendulum.CalculateInclination(_inclination, _angularV, _simTimeStep); 
		//Calculate the angular velocity
		_angularV := _pendulum.CalculateAngularVelocity(_angularV, _inclination, _simTimeStep, _gravitationalFieldStrength);
		
		//Assign maximums
		simData.Inclination := max(abs(_inclination), simData.Inclination);
		simData.AngularVelocity := max(abs(_angularV), simData.AngularVelocity);
		
		
		end;
		
		GetMaxes := simData;
	end;
	
	//Function resets a pre-created simulation object to its initial state
	procedure TPendulumSim.Reset(InitialInclination, InitialAngularVelocity : Real);
        var InitialData : TSimData;
	begin
		_lastExecutionTicks := 0; //Reset the last execution ticks to 0
		_currentTicks := 0; //Reset the current execution ticks to 0
		
		_localDataset.Clear; //Clear the local dataset
				
		//Set initial data
		InitialData := TSimData.Create(0, InitialInclination, InitialAngularVelocity);
		
		//Add initial data to dataset
		_localDataset.Add(InitialData);
	end;
end.
